package com.rizzo.infrastructure.redis.key;

import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.rizzo.infrastructure.amqp.rabbit.log4j.LogStatement;
import com.rizzo.infrastructure.domain.KeyType;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.springframework.util.StringUtils;

/**
 * A POJO representing a Redis-key.
 */
public class RedisKey {

	public static final String REDIS_KEY_DELIMITER = ":";
	public static final String REDIS_WILDCARD = "*";
	public static final String ANONYMOUS = "anonymous";

	private String application;

	private String instanceName;

	private String instanceIndex;

	private KeyType type;

	private String category;

	private String data;

	protected RedisKey(String application) {
		this.application = application;
	}

	public RedisKey(LogStatement logStatement) {
		this.application = logStatement.getApplication();
		this.instanceIndex = String.valueOf(logStatement.getInstanceIndex());
		this.instanceName = logStatement.getInstanceName();
		this.type = KeyType.PERFORMANCE;
		this.category = logStatement.getCategoryName() + "." + logStatement.getMethod();
	}

	public String getApplication() {
		return application;
	}

	public String getInstanceName() {
		return instanceName;
	}

	public String getInstanceIndex() {
		return instanceIndex;
	}

	public String getCategory() {
		return category;
	}

	public KeyType getType() {
		return type;
	}

	public String getData() {
		return data;
	}

	protected void setCategory(String category) {
		this.category = category;
	}

	protected void setInstanceIndex(String instanceIndex) {
		this.instanceIndex = instanceIndex;
	}

	protected void setInstanceName(String instanceName) {
		this.instanceName = instanceName;
	}

	protected void setType(KeyType type) {
		this.type = type;
	}

	protected void setData(String data) {
		this.data = data;
	}

	protected void addData(String data) {
		if (this.data == null) {
			this.data = data;
		} else {
			if (StringUtils.countOccurrencesOf(this.data, REDIS_KEY_DELIMITER) >= 2) {
				throw new IllegalArgumentException("Adding data to the RedisKey can only be performed less than 3 times!");
			} else {
				this.data = this.data + REDIS_KEY_DELIMITER + data;
			}
		}
	}

	public String getFirstData() {
		return Iterables.getFirst(Splitter.on(REDIS_KEY_DELIMITER).split(this.data), "");
	}

	public String getSecondData() {
		return Iterables.get(Splitter.on(REDIS_KEY_DELIMITER).split(this.data), 1, "");
	}

	public String getUserKey() {
		StringBuilder stringBuilder = new StringBuilder();
		if (instanceName != null) {
			stringBuilder.append(REDIS_KEY_DELIMITER).append(instanceName);
		}
		if (instanceIndex != null) {
			stringBuilder.append(REDIS_KEY_DELIMITER).append(instanceIndex);
		}
		if (category != null) {
			stringBuilder.append(REDIS_KEY_DELIMITER).append(category);
		}
		if (data != null) {
			stringBuilder.append(REDIS_KEY_DELIMITER).append(data);
		}
		return stringBuilder.toString();
	}

	public String serialize() {
		StringBuilder stringBuilder = new StringBuilder(application);
		if (type != null) {
			stringBuilder.append(REDIS_KEY_DELIMITER).append(type.getPrefix());
		}
		if (instanceName != null) {
			stringBuilder.append(REDIS_KEY_DELIMITER).append(instanceName);
		}
		if (instanceIndex != null) {
			stringBuilder.append(REDIS_KEY_DELIMITER).append(instanceIndex);
		}
		if (category != null) {
			stringBuilder.append(REDIS_KEY_DELIMITER).append(category);
		}
		if (data != null) {
			stringBuilder.append(REDIS_KEY_DELIMITER).append(data);
		}
		return stringBuilder.toString();
	}

	public static RedisKey deSerialize(String keyString) {
		String[] tokens = StringUtils.delimitedListToStringArray(keyString, REDIS_KEY_DELIMITER);
		RedisKey redisKey;
		KeyType keyType = KeyType.fromPrefix(tokens[1]);
		switch (keyType) {
			case ERROR:
				redisKey = new RedisKeyBuilder().key(tokens[0])
						.type(keyType).data(tokens[2]).build();
				break;
			case CACHE:
				redisKey = new RedisKeyBuilder().key(tokens[0])
						.type(keyType).data(tokens[2]).data(tokens[3]).build();
				break;
			case PERFORMANCE:
				redisKey = new RedisKeyBuilder().key(tokens[0])
						.type(keyType).instanceName(tokens[2]).instanceIndex(tokens[3]).category(tokens[4]).data(tokens[5]).data(tokens[6]).build();
				break;
			case QUOTA:
				redisKey = new RedisKeyBuilder().key(tokens[0])
						.type(keyType).category(tokens[2]).data(tokens[3]).build();
				break;
			case NOTIFICATION:
				redisKey = new RedisKeyBuilder().key(tokens[0])
						.type(keyType).category(tokens[2]).data(tokens[3]).data(tokens[4]).build();
				break;
			case CONFIG:
				redisKey = new RedisKeyBuilder().key(tokens[0])
						.type(keyType).data(tokens[0]).build();
				break;
			case DARK:
				redisKey = new RedisKeyBuilder().key(tokens[0])
						.type(keyType).data(tokens[0]).build();
				break;
			case LOCK:
				redisKey = new RedisKeyBuilder().key(tokens[0])
						.type(keyType).data(tokens[2]).build();
				break;
			default:
				throw new IllegalArgumentException("RedisKey string is invalid! Cannot deserialize!");
		}
		return redisKey;
	}

	public boolean isValid() {
		boolean applicationPresent = StringUtils.hasText(application);
		boolean typePresent = (type != null);
		boolean dataPresent = StringUtils.hasText(data);
		boolean instanceNamePresent = StringUtils.hasText(instanceName);
		boolean instanceIndexPresent = StringUtils.hasText(instanceIndex);
		boolean categoryPresent = StringUtils.hasText(category);

		if (type != null) {
			switch (getType()) {
				case ERROR:
					return applicationPresent && typePresent && dataPresent && !instanceIndexPresent && !instanceNamePresent;
				case CACHE:
					return applicationPresent && typePresent && dataPresent && !instanceIndexPresent && !instanceNamePresent;
				case PERFORMANCE:
					return applicationPresent && typePresent && instanceNamePresent && instanceIndexPresent && categoryPresent && dataPresent;
				case QUOTA:
					return applicationPresent && typePresent && categoryPresent && !instanceIndexPresent && !instanceNamePresent;
				case NOTIFICATION:
					return applicationPresent && typePresent && categoryPresent && dataPresent && !instanceIndexPresent && !instanceNamePresent;
				case CONFIG:
					return applicationPresent && typePresent && !categoryPresent && dataPresent && !instanceIndexPresent && !instanceNamePresent;
				case DARK:
					return applicationPresent && typePresent && !categoryPresent && dataPresent && !instanceIndexPresent && !instanceNamePresent;
				case LOCK:
					return applicationPresent && typePresent && !categoryPresent && dataPresent && !instanceIndexPresent && !instanceNamePresent;
				default:
					return false;
			}
		} else {
			return false;
		}
	}

	@Override
	public boolean equals(Object other) {
		return EqualsBuilder.reflectionEquals(this, other);
	}

	@Override
	public int hashCode() {
		return HashCodeBuilder.reflectionHashCode(this);
	}

	@Override
	public String toString() {
		return "RedisKey{" +
				"application='" + application + '\'' +
				", instanceName='" + instanceName + '\'' +
				", instanceIndex='" + instanceIndex + '\'' +
				", type=" + type +
				", category='" + category + '\'' +
				", data='" + data + '\'' +
				'}';
	}

}
